#include <GL/gl.h>
#include <GL/glu.h>
#include <iostream>
#include <string>

#include "tinyxml/tinyxml.h"
#include "include/ModelSVG.h"
#include "include/ModelX3D.h"
#include "include/LoadModelVisitor.h"
#include "include/Monde.h"
#include "include/Continent.h"
#include "include/Territoire.h"
#include "include/Pion.h"

#define DEFAULT_MAP_PATH "images/WorldRisk.svg"
#define DEFAULT_PION_PATH "models/tour.x3d"
#define DEBUG 0

using namespace std;

GLUtesselator * tobj;
int current_territoire_id = 0;

void beginCallback(GLenum which)
{
   glBegin(which);
}

void errorCallback(GLenum errorCode)
{
   const GLubyte *estring;

   estring = gluErrorString(errorCode);
   fprintf(stderr, "Tessellation Error: %s\n", (char *) estring);
   exit(0);
}

void endCallback(void)
{
   glEnd();
}

void vertexCallback(GLvoid *vertex)
{
   glVertex3dv((GLdouble *)vertex);
}

void combineCallback(GLdouble coords[3],
                     GLdouble *vertex_data[4],
                     GLfloat weight[4], GLdouble **dataOut )
{
   GLdouble * vertex = (GLdouble *) malloc( sizeof(GLdouble) * 3 );

   vertex[0] = coords[0];
   vertex[1] = coords[1];
   vertex[2] = coords[2];

   *dataOut = vertex;
}

LoadModelVisitor::LoadModelVisitor()
   : doc(NULL), current_node(NULL)
{
}

LoadModelVisitor::~LoadModelVisitor()
{
   //printf("Destruction de LoadModelVisitor!\n");
   delete current_node;
   delete doc;
}

string LoadModelVisitor::replace(const char c1, const char c2, string _source)
{
   int i=0;
   int taille=_source.size();
   string ret(_source);

   while(i<taille)
   {
      if(ret[i] == c1)
         ret[i]=c2;
      i++;
   }
   return ret;
}

void LoadModelVisitor::visit(Monde * _monde)
{
   //float _width,_height;
   doc = new TiXmlDocument(DEFAULT_MAP_PATH);
   // initialisation de l'object de tesselation
   tobj = gluNewTess();

   // initialisation des callback de tesselation
   gluTessCallback(tobj, GLU_TESS_VERTEX, (GLvoid (*) ()) &glVertex3dv);
   gluTessCallback(tobj, GLU_TESS_BEGIN, (GLvoid (*) ()) &beginCallback);
   gluTessCallback(tobj, GLU_TESS_END, (GLvoid (*) ()) &endCallback);
   gluTessCallback(tobj, GLU_TESS_ERROR, (GLvoid (*) ()) &errorCallback);
   gluTessCallback(tobj, GLU_TESS_COMBINE, (GLvoid (*) ()) &combineCallback);

   if(DEBUG)
      printf("visite du monde\n");
   const char * monde_filename = DEFAULT_MAP_PATH;
   const char * monde_iterator = "g";

   if(DEBUG)
      printf("chargement du fichier %s\n", monde_filename);
   if( !doc->LoadFile() )
   {
      cerr << "erreur lors du chargement de " << monde_filename << endl;
      cerr << "error #" << doc->ErrorId() << " : " << doc->ErrorDesc() << endl;
      exit(0);
   }
   /*
   if( doc->RootElement()->QueryFloatAttribute("width",&_width) || 
       doc->RootElement()->QueryFloatAttribute("height",&_height))
   {
      cerr << "erreur lors du chargement des dimensions." << endl;
      exit(0);
   }
   _monde->setWidth( _width );
   _monde->setHeight( _height );
   */
   current_node = doc->RootElement()->IterateChildren(monde_iterator, current_node);
   if( !current_node )
   {
      cerr << "erreur lors de la lecture du fichier xml, l'element <";
      cerr << monde_iterator;
      cerr << "> n'est pas defini" << endl;
      exit(0);
   }
   
   while(current_node)
   {
      if(DEBUG)
         printf("ajout du continent: %s ...\n", current_node->ToElement()->Attribute("id"));
      _monde->addContinent( LoadModelVisitor::replace('_', ' ', string( current_node->ToElement()->Attribute("inkscape:label") )).c_str())->accept(this);
      current_node = current_node->NextSiblingElement();
   }
   gluDeleteTess(tobj);
}

void LoadModelVisitor::visit(Continent * _continent)
{
   const char * continent_iterator = "path";
   
   current_node = current_node->FirstChild(continent_iterator);
   if( !current_node )
   {
      cerr << "erreur lors de la lecture du fichier xml, l'element <";
      cerr << continent_iterator;
      cerr << "> n'est pas defini" << endl;
      exit(0);
   }

   while(current_node)
   {
      if(DEBUG)
         printf("\tajout du territoire : %s ...", current_node->ToElement()->Attribute("id"));
      _continent->addTerritoire( LoadModelVisitor::replace('_', ' ', string(current_node->ToElement()->Attribute("id"))).c_str(), current_territoire_id++)->accept(this);
      if( !current_node->NextSibling() )
      {
         current_node = current_node->Parent();
         break;
      }
      current_node = current_node->NextSibling();    
   }
}



void LoadModelVisitor::visit(Territoire * _territoire)
{
   _territoire->getContinent()->getMonde()->addIndexedTerritoire(_territoire);
   ModelSVG::computeShape(current_node->ToElement()->Attribute("d"), _territoire);
   ModelSVG::computeColor(current_node->ToElement()->Attribute("style"), _territoire);
   ModelSVG::computeCenter(current_node->ToElement()->Attribute("center"), _territoire);
   ModelSVG::computeBorders(current_node->ToElement()->Attribute("borders"), _territoire);
   //_territoire->getModel3D()->computeCenter();

   glNewList(*(_territoire->getModel3D()->getDisplayList()), GL_COMPILE);
   
   glShadeModel(GL_SMOOTH);
   gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE);

   gluTessBeginPolygon(tobj, NULL);
   gluTessBeginContour(tobj);
   
   vector<Point3D*>::const_iterator point_ite;
   for(point_ite = _territoire->getModel3D()->getShape()->getPoints()->begin();
       point_ite != _territoire->getModel3D()->getShape()->getPoints()->end();
       point_ite++)
   {
      gluTessVertex(tobj, (double *) *point_ite, (double *) *point_ite);
   }
   
   gluTessEndContour(tobj);     
   gluTessEndPolygon(tobj);  

   glEndList();
}
